# pylint: disable-msg=E1101,E0611,F0401,W0622
"""
@author: Sergiy Tsymbalyuk
"""

from datetime import datetime

from pylons.controllers import WSGIController
from pylons.templating import render_mako as render
from pylons import url, tmpl_context as c, request
from pylons.i18n import lazy_ugettext as _
from pylons.controllers.util import redirect

from sqlalchemy import func, select
from sqlalchemy.sql.expression import desc, and_

from repoze.what.plugins.pylonshq import ActionProtector
from repoze.what.predicates import Any, All, has_permission, in_group

from webhelpers.html import tags

import babel

from chowa.model import District
from chowa.lib.site.navigation import Tab
from chowa.lib.site.settings import get_option, get_personal_option, set_personal_option
from hrms.lib.predicates import from_district

from pubservices.model.meta import Session
from pubservices.model.utilities import Utilities
from pubservices.model.communications import Communications


class BaseController(WSGIController):

    def __call__(self, environ, start_response):
        """Invoke the Controller"""
        # WSGIController.__call__ dispatches to the Controller method
        # the request is routed to. This routing information is
        # available in environ['pylons.routes_dict']
        try:
            return WSGIController.__call__(self, environ, start_response)
        finally:
            Session.remove()


_is_admin = lambda: has_permission('pubservices.administer.records')
_district_clerk = lambda: Any(All(in_group('pubservices.clerks'),
                                  from_district()), _is_admin())
_exp = lambda format, value, value_tenant, area, area_tenant: \
    format % (float(value - value_tenant) / float(area - area_tenant)) \
    if ((area or 0) - (area_tenant or 0)) else '--'

_services = dict(
    water=dict(
        model=Utilities,
        columns=['water_volume',
                 'water_amount',
                 'water_volume_tenant',
                 'water_amount_tenant',
                 'staff'],
        thead=[[(_('Total'), 2), (_('Tenant'), 2), (_('Own'), 2), (_('Per employee'), 2)],
               [(_('Volume, m3'), 1), (_('Amount, uah'), 1)] * 4],
        row=lambda r: [r.water_volume,
                       '%.2f' % r.water_amount,
                       r.water_volume_tenant,
                       '%.2f' % r.water_amount_tenant,
                       r.water_volume - r.water_volume_tenant,
                       '%.2f' % (r.water_amount - r.water_amount_tenant),
                       '%.3f' % ((r.water_volume - r.water_volume_tenant)/r.staff) if r.staff else '--',
                       '%.2f' % ((r.water_amount - r.water_amount_tenant)/r.staff) if r.staff else '--']),
    electricity=dict(
        model=Utilities,
        columns=['elec_energy',
                 'elec_amount',
                 'elec_energy_tenant',
                 'elec_amount_tenant',
                 'staff'],
        thead=[[(_('Total'), 2), (_('Tenant'), 2), (_('Own'), 2), (_('Per employee'), 2)],
               [(_('Energy, kW'), 1), (_('Amount, uah'), 1)] * 4],
        row=lambda r: ['%d' % r.elec_energy,
                       '%.2f' % r.elec_amount,
                       '%d' % r.elec_energy_tenant,
                       '%.2f' % r.elec_amount_tenant,
                       '%d' % (r.elec_energy - r.elec_energy_tenant),
                       '%.2f' % (r.elec_amount - r.elec_amount_tenant),
                       '%.1f' % ((r.elec_energy - r.elec_energy_tenant)/r.staff) if r.staff else '--',
                       '%.2f' % ((r.elec_amount - r.elec_amount_tenant)/r.staff) if r.staff else '--']),
    heating=dict(
        model=Utilities,
        columns=['heat_energy',
                 'heat_amount',
                 'area',
                 'heat_energy_tenant',
                 'heat_amount_tenant',
                 'area_tenant'],
        thead=[[(_('Total'), 2), (_('Tenant'), 2), (_('Own'), 2), (_('Per m2'), 2)],
               [(_('Energy, Gcal'), 1), (_('Amount, uah'), 1)] * 4],
        row=lambda r: [r.heat_energy,
                       '%.2f' % r.heat_amount,
                       r.heat_energy_tenant,
                       '%.2f' % r.heat_amount_tenant,
                       r.heat_energy - r.heat_energy_tenant,
                       '%.2f' % (r.heat_amount - r.heat_amount_tenant),
                       _exp('%.3f', r.heat_energy, r.heat_energy_tenant, r.area, r.area_tenant),
                       _exp('%.2f', r.heat_amount, r.heat_amount_tenant, r.area, r.area_tenant)]),
    gas=dict(
        model=Utilities,
        columns=['gas_volume',
                 'gas_amount',
                 'area',
                 'gas_volume_tenant',
                 'gas_amount_tenant',
                 'area_tenant'],
        thead=[[(_('Total'), 2), (_('Tenant'), 2), (_('Own'), 2), (_('Per m2'), 2)],
               [(_('Volume, m3'), 1), (_('Amount, uah'), 1)] * 4],
        row=lambda r: [r.gas_volume,
                       r.gas_amount,
                       r.gas_volume_tenant,
                       r.gas_amount_tenant,
                       r.gas_volume - r.gas_volume_tenant,
                       r.gas_amount - r.gas_amount_tenant,
                       _exp('%.3f', r.gas_volume, r.gas_volume_tenant, r.area, r.area_tenant),
                       _exp('%.2f', r.gas_amount, r.gas_amount_tenant, r.area, r.area_tenant)]),
    phone=dict(
        model=Communications,
        columns=['budget',
                 'phones',
                 'subscription',
                 'local_amount',
                 'phone_install'],
        thead=[[(_('Budget'), 1),
                (_('Phones'), 1),
                (_('Subscription'), 1),
                (_('Amount'), 1),
                (_('Installation'), 1)]],
        row=lambda r: ['%.2f' % r.budget,
                       r.phones,
                       '%.2f' % r.subscription,
                       '%.2f' % r.local_amount,
                       '%.2f' % r.phone_install]),
    long_distance=dict(
        model=Communications,
        columns=['ldts_calls',
                 'ldts_seconds',
                 'ldts_amount'],
        thead=[[(_('Calls'), 1), (_('Seconds'), 1), (_('Amount'), 1)]],
        row=lambda r: [r.ldts_calls,
                       r.ldts_seconds,
                       '%.2f' % r.ldts_amount]),
    various=dict(
        model=Communications,
        columns=['channel',
                 'adsl',
                 'fax',
                 'login',
                 'security',
                 'radio',
                 'other'],
        thead=[[(_('Channel'), 1),
                (_('ADSL'), 1),
                (_('Fax'), 1),
                (_('Security'), 1),
                (_('Login'), 1),
                (_('Radio'), 1),
                (_('Other'), 1)]],
        row=lambda r: ['%.2f' % r.channel,
                       '%.2f' % r.adsl,
                       '%.2f' % r.fax,
                       '%.2f' % r.login,
                       '%.2f' % r.security,
                       '%.2f' % r.radio,
                       '%.2f' % r.other]),
)


class PublicServicesController(BaseController):
    __secondary_tabs__ = [
        [
            Tab(params=dict(action='analysis', analytics='period'),
                caption=_('Period'),
                description=_('Period')
            ),
            Tab(params=dict(action='analysis', analytics='comparison'),
                caption=_('Comparison'),
                description=_('Comparison')
            ),
        ],
        [
            Tab(params=dict(action='total'),
                caption=_('Total'),
                description=_('Total')
            ),
            Tab(params=dict(action='period'),
                caption=_('Period'),
                description=_('Period')
            ),
            Tab(params=dict(action='editing'),
                caption=_('Editing'),
                description=_('Editing')
            ),
        ]
    ]
    
    _services = []
    
    def __before__(self, id=None, service=None):
        if service:
            self.model = _services[service]['model']
            
        self.current_year = get_option('pubservices.current_period.year')
        self.current_month = get_option('pubservices.current_period.month')
        
        c.district = Session.query(District).get(id) if id else None
        c.service = service
    
    def _is_read_only(self, is_current):
        ro_current = get_option('pubservices.read_only.current')
        ro_previous = get_option('pubservices.read_only.previous')
        return (is_current and ro_current) or (not is_current and ro_previous)
    
    def _get_columns(self, service):
        service_columns = _services[service]['columns']
        columns = [self.model.__table__.columns.get(column)
                        for column in service_columns
                        if self.model.__table__.columns.has_key(column)]
        return columns
    
    @ActionProtector(_district_clerk())
    def editing(self, id, service):
        """View an editing table."""
        query = Session.query(self.model)
        
        year = request.params.get('year') or datetime.now().year
        years_query = select([self.model.year]).group_by('year')
        c.years = [y.year for y in Session.execute(years_query)]
        current_year = datetime.now().year
        if not current_year in c.years:
            c.years += [current_year]
        c.year = year
        
        c.records = query.filter_by(district_id=id, year=year) \
            .order_by(desc('month'), desc('decade'))
        
        decades = [record.month * 4 + record.decade for record in c.records]
        all_decades = [month * 4 + decade
                       for month in range(1, 13)
                       for decade in range(4)]
        no_decades = [decade for decade in all_decades if decade not in decades]
        
        new_month = 12
        new_decade = 0
        if no_decades:
            new_month = min(no_decades) / 4
            new_decade = min(no_decades) % 4
            
        month_names = babel.dates.get_month_names('abbreviated', context='stand-alone')
        try:
            min_year = min(c.years)
        except:
            min_year = datetime.now().year
        c.year_select = tags.select('year', [year],
                                    range(min_year, datetime.now().year + 1))
        c.month_select = tags.select('month', [new_month],
                                     [(i, month_names[i]) for i in month_names])
        c.decade_select = tags.select('decade', [new_decade],
                                      [(0, _('month'))] + [(i, '%d %s' % (i, _('decade').__unicode__()))
                                                           for i in [1, 2, 3]])
        c.columns = self._get_columns(service)
        return render('/pubservices/editing/editing.html')
    
    @ActionProtector(_district_clerk())
    def add(self, id, service):
        """Add a new record and back to table."""
        year = request.params['year']
        month = request.params['month']
        decade = request.params['decade']
        record = Session.query(self.model).filter_by(year=year,
                                                     month=month,
                                                     decade=decade,
                                                     district_id=id).first()
        if not record:
            record = self.model()
            record.year = year
            record.month = month
            record.decade = decade
            record.district_id = id
        for column in _services[service]['columns']:
            setattr(record, column, request.params[column])
        is_current = (record.year == self.current_year) and (record.month == self.current_month)
        if not self._is_read_only(is_current) or _is_admin().is_met(request.environ):
            Session.add(record)
            Session.commit()
        return redirect(url.current(action='editing', year=request.params['year']))
    
    @ActionProtector(_district_clerk())
    def save(self, id, service):
        """Update record data. Asynchronous action."""
        query = Session.query(self.model)
        record = query.get(request.params['id'])
        
        is_current = (record.year == self.current_year) and \
                     (record.month == self.current_month)
        if not self._is_read_only(is_current) or _is_admin().is_met(request.environ):
            for column in _services[service]['columns']:
                setattr(record, column, request.params[column])
            Session.commit()
                
        c.record = record
        c.columns = self._get_columns(service)
        return render('/pubservices/editing/table_row.html')
    
    @ActionProtector(_district_clerk())
    def total(self, id, service):
        query = Session.query(self.model)
        records = query.filter_by(district_id=id, decade=0).all()
        
        query = Session.query(self.model.year, func.max(13).label('month'))
        columns = self.model.__table__.columns
        for column_name in columns.keys():
            column = columns.get(column_name)
            if column.info.has_key('total'):
                f = getattr(func, column.info['total'])
                query = query.add_columns(f(column).label(column_name))
        total = query.filter_by(district_id=id, decade=0) \
            .group_by('year').all()
        records += total
        records = sorted(records, key=lambda record: record.month)
        c.records = sorted(records, key=lambda record: record.year)
        
        c.thead = _services[service]['thead']
        c.row = _services[service]['row']
        return render('/pubservices/total.html')
    
    @ActionProtector(_district_clerk())
    def period(self, id, service):
        period = self._init_period('')
        self._init_period_form('')
        
        begin = int(period['begin']['year']) * 12 + int(period['begin']['month'])
        end = int(period['end']['year']) * 12 + int(period['end']['month'])
        
        filt = lambda q: q.filter((self.model.year * 12 + self.model.month) >= begin) \
                          .filter((self.model.year * 12 + self.model.month) <= end) \
                          .filter_by(decade=0, district_id=id)
        
        c.records = filt(Session.query(self.model)).all()
        
        query = Session.query(self.model.decade)
        query = query.add_columns(func.max(0).label('month'), func.max(0).label('year'))
        columns = self.model.__table__.columns
        for column_name in columns.keys():
            column = columns.get(column_name)
            if column.info.has_key('total'):
                f = getattr(func, column.info['total'])
                query = query.add_columns(f(column).label(column_name))
        query = filt(query)
        total = query.group_by('district_id').order_by('year', 'month').all()
        c.records += total
        
        c.thead = _services[service]['thead']
        c.row = _services[service]['row']
        return render('/pubservices/period.html')
    
    @ActionProtector(_is_admin())
    def analysis(self, service, analytics):
        """Analysis actions dispatcher."""
        return getattr(self, '_analysis_%s' % analytics)(service)
        
    def _analysis_period(self, service):
        columns = _services[service]['columns']
        
        period = self._init_period('')
        self._init_period_form('')
        c.records = [record for record in self._get_period_data(service, period)]
        class _Record(object): pass
        c.total = _Record()
        for record in c.records:
            for column in columns:
                if not getattr(c.total, column, None):
                    setattr(c.total, column, 0)
                setattr(c.total, column, getattr(c.total, column) + (record[column] or 0))
        c.thead = _services[service]['thead']
        c.row = _services[service]['row']
        return render('/pubservices/analysis/period.html')
    
    def _analysis_comparison(self, service):
        columns = _services[service]['columns']
        
        period1 = self._init_period('p1_')
        period2 = self._init_period('p2_')
        self._init_period_form('p1_')
        self._init_period_form('p2_')
        period1_data = self._get_period_data(service, period1)
        period2_data = self._get_period_data(service, period2)
        
        class _object(object): pass
        c.records = {}
        for record in period2_data:
            row = _object()
            row.name = record.colloquial
            for column in columns:
                setattr(row, column, record[column])
            c.records[record.district_id] = row
        for record in period1_data:
            if c.records.has_key(record.district_id):
                row = c.records[record.district_id]
                for column in columns:
                    value1 = 0 if record[column] == None else record[column]
                    value2 = 0 if getattr(row, column) == None else getattr(row, column)
                    value = value2 - value1
                    setattr(row, column, value)
            else:
                row = _object()
                row.name = record.colloquial
                for column in columns:
                    setattr(row, column, -record[column])
                c.records[record.district_id] = row
        c.thead = _services[service]['thead']
        c.row = _services[service]['row']
        return render('/pubservices/analysis/comparison.html')
    
    def _get_period_data(self, service, period):
        model = self.model
        begin = int(period['begin']['year']) * 12 + int(period['begin']['month'])
        end = int(period['end']['year']) * 12 + int(period['end']['month'])

        columns = [District.colloquial, model.__table__]
        for column_name in _services[service]['columns']:
            column = self.model.__table__.columns.get(column_name)
            f = getattr(func, column.info['total'])
            columns += [f(column).label(column_name)]
        query = select(columns) \
            .where(
                and_(
                    (model.year * 12 + model.month) >= begin,
                    (model.year * 12 + model.month) <= end,
                )) \
            .where(model.decade == 0) \
            .where(model.district_id == District.id) \
            .group_by(District.id)
        return Session.execute(query)
    
    def _init_period(self, prefix):
        period = {}
        for point in ['begin', 'end']:
            period[point] = {}
            for unit in ['year', 'month']:
                period[point][unit] = getattr(datetime.now(), unit)
                if (point == 'begin') & (unit == 'year'):
                    period[point][unit] -= 1
                key = '%s%s_%s' % (prefix, point, unit)
                if request.params.has_key(key):
                    value = request.params.get(key)
                    period[point][unit] = value
                    set_personal_option('pubservices.' + key, value)
                else:
                    period[point][unit] = get_personal_option('pubservices.' + key) or 0
        return period
    
    def _init_period_form(self, prefix):
        period = self._init_period(prefix)
        month_names = babel.dates.get_month_names('abbreviated', context='stand-alone')
        
        def set_ctx_var(point, unit, options):
            select_name = '%s%s_%s' % (prefix, point, unit)
            select_tag = tags.select(select_name, [period[point][unit]], options, class_='submit')
            setattr(c, '%s_select' % select_name, select_tag)
        set_ctx_var('begin', 'year', range(2003, datetime.now().year + 1))
        set_ctx_var('begin', 'month', [(i, month_names[i]) for i in month_names])
        set_ctx_var('end', 'year', range(2003, datetime.now().year + 1))
        set_ctx_var('end', 'month', [(i, month_names[i]) for i in month_names])
